﻿using UnityEngine;
using UniRx;
using System.Threading.Tasks;

public class PlayerFSM : FSMBase
{
    protected override string getName => "player";

    private FSMStateStand standState = new FSMStateStand();
    private FSMStateSquat squatState = new FSMStateSquat();
    private FSMStateAirborne airborneState = new FSMStateAirborne();
    private FSMStateClimb climbState = new FSMStateClimb();
    private FSMStateJump jumpState = new FSMStateJump();
    private FSMStateJumpAcross jumpAcrossState = new FSMStateJumpAcross();
    private FSMStateJumpLeave jumpLeaveState = new FSMStateJumpLeave();
    private FSMStateStandSkill standSkillState = new FSMStateStandSkill();
    private FSMStateAirborneSkill airborneSkillState = new FSMStateAirborneSkill();
    private FSMStateHurt hurtState = new FSMStateHurt();
    private FSMStateDie dieState = new FSMStateDie();

    private float m_HorizontalEasingAxis;
    private float m_VerticalEasingAxis;

    protected override void InitParameters()
    {
        entity.controller.GetStream<float>("horizontal")
            .Subscribe(v => parameters.SetParam("horizontalAxis", v))
            .AddTo(entity);

        entity.controller.GetStream<float>("vertical")
            .Subscribe(v => parameters.SetParam("verticalAxis", v))
            .AddTo(entity);

        entity.controller.GetStream<int>("standSkill")
            .Subscribe(v => parameters.SetParam("standSkill", v))
            .AddTo(entity);

        entity.controller.GetStream<int>("airborneSkill")
            .Subscribe(v => parameters.SetParam("airborneSkill", v))
            .AddTo(entity);

        entity.controller.GetStream<Unit>("interact")
            .Subscribe(_ => InteractAsync())
            .AddTo(entity);

        var cimberChecker = entity.physics.climberChecker;
        entity.lifeCycle.GetLifeCycleStream(UnitLifeCycle.LifeType.FSMCondition).WhenActive(entity)
            .Subscribe(_ =>
            {
                parameters.SetParam("isGround", entity.physics.groundChecker.isGround);
                parameters.SetParam("isRising", entity.physics.velocity.y > 0);
                parameters.SetParam("isClimber", cimberChecker.isClimber);
                parameters.SetParam("isClimberTop", cimberChecker.isClimberTop);
                parameters.SetParam("isClimberBottom", cimberChecker.isClimberBottom);

                m_HorizontalEasingAxis = Mathf.MoveTowards(m_HorizontalEasingAxis, parameters.GetParam<float>("horizontalAxis"), Time.deltaTime / UnitDefine.BASE_ACCE_TIME);
                entity.animator.animator.SetFloat("horizontalAxis", m_HorizontalEasingAxis);

                m_VerticalEasingAxis = Mathf.MoveTowards(m_VerticalEasingAxis, parameters.GetParam<float>("verticalAxis"), Time.deltaTime / UnitDefine.BASE_ACCE_TIME);
                entity.animator.animator.SetFloat("verticalAxis", m_VerticalEasingAxis);

                entity.animator.animator.SetFloat("velocityY", entity.physics.velocity.y);
            })
            .AddTo(entity);
    }

    protected override void OnInit()
    {
        base.OnInit();

        RegisterState(standState, true);
        RegisterState(squatState);
        RegisterState(airborneState);
        RegisterState(climbState);
        RegisterState(jumpState);
        RegisterState(jumpAcrossState);
        RegisterState(jumpLeaveState);
        RegisterState(standSkillState);
        RegisterState(airborneSkillState);
        RegisterState(hurtState);
        RegisterState(dieState);

        RegisterTransition(standState, squatState, () => parameters.GetParam<float>("verticalAxis") < 0);
        RegisterTransition(squatState, standState, () => parameters.GetParam<float>("verticalAxis") >= 0);

        RegisterTransition(standState, jumpState, entity.controller.GetStream<Unit>("jump").AsUnitObservable());
        RegisterTransition(jumpState, airborneState);
        RegisterTransition(standState, airborneState, () => !parameters.GetParam<bool>("isGround"));
        RegisterTransition(airborneState, standState, () => parameters.GetParam<bool>("isGround") && !parameters.GetParam<bool>("isRising") && parameters.GetParam<float>("verticalAxis") >= 0);

        RegisterTransition(standState, climbState, () => parameters.GetParam<bool>("isClimber") && !parameters.GetParam<bool>("isClimberTop") && parameters.GetParam<float>("verticalAxis") > 0);
        RegisterTransition(climbState, standState, () => parameters.GetParam<bool>("isGround") && parameters.GetParam<bool>("isClimberBottom") && parameters.GetParam<float>("verticalAxis") < 0);
        RegisterTransition(climbState, standState, () => parameters.GetParam<bool>("isGround") && parameters.GetParam<bool>("isClimberTop") && parameters.GetParam<float>("verticalAxis") > 0);

        RegisterTransition(standState, standSkillState, entity.controller.GetStream<int>("standSkill").AsUnitObservable());
        RegisterTransition(standSkillState, standState);

        RegisterTransition(airborneState, airborneSkillState, entity.controller.GetStream<int>("airborneSkill").AsUnitObservable());
        RegisterTransition(airborneSkillState, airborneState);

        RegisterTransition(squatState, jumpAcrossState, entity.controller.GetStream<Unit>("jump").AsUnitObservable(), () => entity.physics.groundChecker.canJumpAcross && parameters.GetParam<float>("verticalAxis") < 0);
        RegisterTransition(jumpAcrossState, airborneState);
        RegisterTransition(airborneState, squatState, () => parameters.GetParam<bool>("isGround") && !parameters.GetParam<bool>("isRising") && parameters.GetParam<float>("verticalAxis") < 0);

        RegisterTransition(squatState, climbState, () => parameters.GetParam<bool>("isClimber") && parameters.GetParam<bool>("isClimberTop") && parameters.GetParam<float>("verticalAxis") < 0);

        RegisterTransition(airborneState, climbState, () => parameters.GetParam<bool>("isClimber") && parameters.GetParam<float>("verticalAxis") > 0);
        RegisterTransition(climbState, airborneState, () => !parameters.GetParam<bool>("isClimber") && !parameters.GetParam<bool>("isGround"));
        RegisterTransition(climbState, jumpLeaveState, entity.controller.GetStream<Unit>("jump").AsUnitObservable(), () => parameters.GetParam<float>("horizontalAxis") != 0);
        RegisterTransition(jumpLeaveState, airborneState);

        RegisterGlobalTransition(hurtState, condition: () => parameters.GetParam<float>("hurtAxis") != 0 && entity.attribute.currentHP > 0);
        RegisterTransition(hurtState, standState, () => parameters.GetParam<bool>("isGround"));
        RegisterTransition(hurtState, airborneState, () => !parameters.GetParam<bool>("isGround"));

        RegisterGlobalTransition(dieState, condition: () => parameters.GetParam<float>("hurtAxis") != 0 && entity.attribute.currentHP <= 0);
    }

    private static readonly LayerMask PORTAL_LAYER = 1 << LayerMask.NameToLayer("Interactable");
    private void InteractAsync()
    {
        var collider = Physics2D.OverlapBox(entity.transform.position, entity.physics.box.size, 0, PORTAL_LAYER);
        if (collider == null) return;
        var portalInfo = collider.GetComponent<Portal>();
        if (portalInfo == null) return;
        SceneOrganizer.instance.FindPortal(portalInfo.id).Enter();
    }
}